今天用前面做過的小畫家相似功能,來完成一個可以在文件上面簽名的功能~
當然也會有新的東西可以玩。
我們將文件簽名分成三個步驟
<div className="App">
<h1>步驟一</h1>
<h3>繪製簽名檔</h3>
<SignBox></SignBox>
<h1>步驟二</h1>
<h3>上傳文件</h3>
<UploadFile></UploadFile>
<h1>步驟三</h1>
<h3>合併輸出</h3>
<Output></Output>
</div>
今天先教學 繪製簽名檔
及 上傳文件
的部分
根據以前的操作,相信已經對 mouse 的事件滾瓜爛熟了,
這裡就不贅述囉。
code
import React, { useEffect, useRef, useState } from "react";
import getTouchPos from "../../utils/getTouchPos";
import getMousePos from "../../utils/getMousePos";
import { useAtom } from "jotai";
import { signAtom } from "../../data";
const canvasSize = 500;
const SignFile = () => {
const canvasRef = useRef(null);
const [canvas, setCanvas] = useState(null);
const [ctx, setCtx] = useState(null);
const [src, setSrc] = useState(null);
const [drawing, setDrawing] = useState(false);
const [_, setSignData] = useAtom(signAtom);
useEffect(() => {
const c = canvasRef.current;
setCanvas(c);
if (c) setCtx(c.getContext("2d"));
}, [canvasRef]);
/** 開始 */
const handleTouchStart = (event) => {
setDrawing(true);
const touchPos = getTouchPos(canvas, event);
ctx.beginPath(touchPos.x, touchPos.y);
ctx.moveTo(touchPos.x, touchPos.y);
event.preventDefault();
};
const handleMouseDown = (event) => {
setDrawing(true);
const mousePos = getMousePos(canvas, event);
ctx.beginPath();
ctx.moveTo(mousePos.x, mousePos.y);
event.preventDefault();
};
/** 移動 */
const handleTouchMove = (event) => {
if (!drawing) return;
const touchPos = getTouchPos(canvas, event);
ctx.lineWidth = 2;
ctx.lineCap = "round"; // 繪制圓形的結束線帽
ctx.lineJoin = "round"; // 兩條線條交匯時,建立圓形邊角
ctx.shadowBlur = 1; // 邊緣模糊,防止直線邊緣出現鋸齒
ctx.shadowColor = "black"; // 邊緣顏色
ctx.lineTo(touchPos.x, touchPos.y);
ctx.stroke();
};
const handleMouseMove = (event) => {
if (!drawing) return;
const mousePos = getMousePos(canvas, event);
ctx.lineWidth = 2;
ctx.lineCap = "round"; // 繪制圓形的結束線帽
ctx.lineJoin = "round"; // 兩條線條交匯時,建立圓形邊角
ctx.shadowBlur = 1; // 邊緣模糊,防止直線邊緣出現鋸齒
ctx.shadowColor = "black"; // 邊緣顏色
ctx.lineTo(mousePos.x, mousePos.y);
ctx.stroke();
};
/** 結束 */
const handleTouchEnd = (event) => {
setDrawing(false);
};
const handleMouseUp = (event) => {
setDrawing(false);
};
/** 清除 */
const handleClear = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
};
/** 轉圖片 */
const handleConvertToImage = () => {
const image = canvas.toDataURL();
setSignData(image);
setSrc(image);
};
return (
<div>
<canvas
style={{ background: "#EEE" }}
ref={canvasRef}
width={canvasSize}
height={canvasSize}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
></canvas>
<div>
<button onClick={handleClear}>清除</button>
<button onClick={handleConvertToImage}>轉圖</button>
</div>
{src && (
<img
src={src}
alt="signImage"
style={{ color: "#FFF", border: "none" }}
/>
)}
</div>
);
};
export default SignFile;
補充一下,這次跨組件都用 jotai
,想要用 recoil
之類的都是可行的。
迅速的就完成畫簽名啦,而且同時支援滑鼠
與觸控
事件。
做文件簽名當然也需要文件的支持啦,這段是本篇章的重點!
我們需要學習新的lib pdfjs
,迅速地讓他幫我們做到 pdf to canvas
至於怎麼使用,就請大家去翻翻文檔吧
import React, { useEffect, useRef, useState } from "react";
import getScaledDim from "../../utils/getScaledDim";
import { useAtom } from "jotai";
import { bgFileAtom } from "../../data";
import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
const canvasSize = 500;
const UploadFile = () => {
const canvasRef = useRef(null);
const [canvas, setCanvas] = useState(null);
const [ctx, setCtx] = useState(null);
const [src, setSrc] = useState(null);
const [bgFileData, setBgFileData] = useAtom(bgFileAtom);
useEffect(() => {
const c = canvasRef.current;
setCanvas(c);
if (c) setCtx(c.getContext("2d"));
}, [canvasRef]);
/** image */
const handleUploadImage = (event) => {
const f = event.target.files[0];
const ctx = canvasRef.current.getContext("2d");
const img = new Image();
img.onload = function () {
const scaled = getScaledDim(img, canvasSize, canvasSize);
// scale canvas to image
ctx.width = scaled.width;
ctx.height = scaled.height;
// draw image
ctx.drawImage(img, 0, 0, ctx.width, ctx.height);
};
img.src = URL.createObjectURL(f);
};
/** pdf */
const handleUploadPdf = (event) => {
const file = event.target.files[0];
if (file.type === "application/pdf") {
let fileReader = new FileReader();
fileReader.onload = function () {
const pdfData = new Uint8Array(this.result);
// Using DocumentInitParameters object to load binary data.
const loadingTask = pdfjs.getDocument({ data: pdfData });
loadingTask.promise.then(
function (pdf) {
console.log("PDF loaded");
// Fetch the first page
const pageNumber = 1;
pdf.getPage(pageNumber).then(function (page) {
console.log("Page loaded");
const scale = 1.5;
const viewport = page.getViewport({ scale: scale });
// Prepare canvas using PDF page dimensions
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
const renderTask = page.render(renderContext);
renderTask.promise.then(function () {
console.log("Page rendered");
});
});
},
function (reason) {
// PDF loading error
console.error(reason);
}
);
};
fileReader.readAsArrayBuffer(file);
}
};
const handleConvertToImage = () => {
const image = canvas.toDataURL();
setBgFileData(image);
setSrc(image);
};
return (
<div>
<div style={{ marginBottom: `1rem` }}>
上傳 Image:
<input type="file" onChange={handleUploadImage} />
</div>
<div>
上傳 PDF:
<input accept=".pdf" type="file" onChange={handleUploadPdf} />
</div>
<canvas ref={canvasRef} width={canvasSize} height={canvasSize}></canvas>
<button onClick={handleConvertToImage}>轉圖</button>
<img src={src} alt="imagePdf" />
</div>
);
};
export default UploadFile;
// https://mozilla.github.io/pdf.js/examples/index.html#interactive-examples
這樣材料就算是準備好了,明天來準備合併吧!!